/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
*    http://www.geo-solutions.it/
*    Copyright 2014 GeoSolutions


* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

* http://www.apache.org/licenses/LICENSE-2.0

* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.imagen.media.rescale;

import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.ParameterBlock;
import java.awt.image.renderable.RenderableImage;
import org.eclipse.imagen.JAI;
import org.eclipse.imagen.OperationDescriptorImpl;
import org.eclipse.imagen.ParameterBlockJAI;
import org.eclipse.imagen.PropertyGenerator;
import org.eclipse.imagen.ROI;
import org.eclipse.imagen.ROIShape;
import org.eclipse.imagen.RenderableOp;
import org.eclipse.imagen.RenderedOp;
import org.eclipse.imagen.media.range.Range;
import org.eclipse.imagen.media.util.PropertyGeneratorImpl;
import org.eclipse.imagen.registry.RenderableRegistryMode;
import org.eclipse.imagen.registry.RenderedRegistryMode;

/**
 * This class is used for retrieving an eventual ROI object passed to the source image by calling the getProperty()
 * method. This method checks if the ROI is present and if so, its bounds are intersected with the source images bounds,
 * and then passed as a result. If no property was found an Undefined Property object is returned.
 */
class RescalePropertyGenerator extends PropertyGeneratorImpl {

    /** Constructor. */
    public RescalePropertyGenerator() {
        super(new String[] {"ROI"}, new Class[] {ROI.class}, new Class[] {RenderedOp.class});
    }

    /** Returns the ROI saved as a property. */
    public Object getProperty(String name, Object opNode) {
        validate(name, opNode);

        if (opNode instanceof RenderedOp && name.equalsIgnoreCase("roi")) {
            RenderedOp op = (RenderedOp) opNode;

            ParameterBlock pb = op.getParameterBlock();

            // Retrieve the rendered source image and its ROI.
            RenderedImage src = pb.getRenderedSource(0);
            Object property = src.getProperty("ROI");
            if (property == null || property.equals(java.awt.Image.UndefinedProperty) || !(property instanceof ROI)) {
                return java.awt.Image.UndefinedProperty;
            }

            ROI srcROI = (ROI) property;
            // Determine the effective source bounds.
            Rectangle srcBounds = null;

            srcBounds = new Rectangle(src.getMinX(), src.getMinY(), src.getWidth(), src.getHeight());

            // If necessary, clip the ROI to the effective source bounds.
            if (!srcBounds.contains(srcROI.getBounds())) {
                srcROI = srcROI.intersect(new ROIShape(srcBounds));
            }

            // Saves the destination ROI.
            ROI dstROI = srcROI;

            // Return the clipped ROI.
            return dstROI;
        }
        return java.awt.Image.UndefinedProperty;
    }
}

/**
 * An <code>OperationDescriptor</code> describing the "Rescale" operation.
 *
 * <p>The "Rescale" operation takes a rendered or renderable source image and changes the image dynamics by multiplying
 * each pixel value by a constant and then adding another constant to the result of the multiplication. Each constant
 * value is associated to a band. If the number of constants supplied is less than the number of bands of the
 * destination, then the constant from entry 0 is applied to all the bands. Otherwise, a constant from a different entry
 * is applied to each band. The optional presence of NoData or ROI is taken into account by replacing each value out of
 * ROI or each NoData, with the supplied DestinationNoData value.
 *
 * <p>The destination pixel values are defined by the following pseudocode:
 *
 * <pre>
 * dst = destination pixel array
 * src = source pixel array
 *
 * dst[x][y][b] = src[x][y][b] * constant + offset;
 * </pre>
 *
 * <p>The pixel arithmetic is performed using the data type of the destination image. By default, the destination will
 * have the same data type as the source image unless an <code>ImageLayout</code> containing a <code>SampleModel</code>
 * with a different data type is supplied as a rendering hint.
 *
 * <p>
 *
 * <table border=1>
 * <caption>Resource List</caption>
 * <tr>
 * <th>Name</th>
 * <th>Value</th>
 * </tr>
 * <tr>
 * <td>GlobalName</td>
 * <td>Rescale</td>
 * </tr>
 * <tr>
 * <td>LocalName</td>
 * <td>Rescale</td>
 * </tr>
 * <tr>
 * <td>Vendor</td>
 * <td>org.eclipse.imagen.media</td>
 * </tr>
 * <tr>
 * <td>Description</td>
 * <td>Operation which converts the image dynamic to a new dynamic.</td>
 * </tr>
 * <tr>
 * <td>DocURL</td>
 * <td>Not Defined</td>
 * </tr>
 * <tr>
 * <td>Version</td>
 * <td>1.0</td>
 * </tr>
 * <tr>
 * <td>arg0Desc</td>
 * <td>Scale factors used for rescaling values.</td>
 * </tr>
 * <tr>
 * <td>arg1Desc</td>
 * <td>Offset factors used for rescaling values.</td>
 * </tr>
 * <tr>
 * <td>arg2Desc</td>
 * <td>ROI object used.</td>
 * </tr>
 * <tr>
 * <td>arg3Desc</td>
 * <td>No Data Range used.</td>
 * </tr>
 * <tr>
 * <td>arg4Desc</td>
 * <td>Boolean checking if ROI RasterAccessor is used.</td>
 * </tr>
 * <tr>
 * <td>arg5Desc</td>
 * <td>Destination No Data value.</td>
 * </tr>
 * </table>
 *
 * <p>
 *
 * <table border=1>
 * <caption>Parameter List</caption>
 * <tr>
 * <th>Name</th>
 * <th>Class Type</th>
 * <th>Default Value</th>
 * </tr>
 * <tr>
 * <td>scale</td>
 * <td>double[]</td>
 * <td>{1.0}</td>
 * <tr>
 * <td>offset</td>
 * <td>double[]</td>
 * <td>{0.0}</td>
 * <tr>
 * <td>ROI</td>
 * <td>org.eclipse.imagen.ROI</td>
 * <td>null</td>
 * <tr>
 * <td>noData</td>
 * <td>org.eclipse.imagen.media.range.Range</td>
 * <td>null</td>
 * <tr>
 * <td>useRoiAccessor</td>
 * <td>Boolean</td>
 * <td>false</td>
 * <tr>
 * <td>destNoData</td>
 * <td>Double</td>
 * <td>0.0d</td>
 * </table>
 */
@SuppressWarnings("serial")
public class RescaleDescriptor extends OperationDescriptorImpl {

    /**
     * The resource strings that provide the general documentation and specify the parameter list for this operation.
     */
    private static final String[][] resources = {
        {"GlobalName", "Rescale"},
        {"LocalName", "Rescale"},
        {"Vendor", "org.eclipse.imagen.media"},
        {"Description", "Operation which converts the image dynamic to a new dynamic"},
        {"DocURL", "Not Defined"},
        {"Version", "1.0"},
        {"arg0Desc", "Scale factors used for rescaling values"},
        {"arg1Desc", "Offset factors used for rescaling values"},
        {"arg2Desc", "ROI object used"},
        {"arg3Desc", "No Data Range used"},
        {"arg4Desc", "Boolean checking if ROI RasterAccessor is used"},
        {"arg5Desc", "Destination No Data value"}
    };

    /** The parameter class list for this operation. */
    private static final Class[] paramClasses = {
        double[].class,
        double[].class,
        org.eclipse.imagen.ROI.class,
        org.eclipse.imagen.media.range.Range.class,
        Boolean.class,
        Double.class
    };

    /** The parameter name list for this operation. */
    private static final String[] paramNames = {"constants", "offsets", "ROI", "noData", "useRoiAccessor", "destNoData"
    };

    /** The parameter default value list for this operation. */
    private static final Object[] paramDefaults = {new double[] {1.0}, new double[] {0.0}, null, null, false, 0.0d};

    /** Constructor. */
    public RescaleDescriptor() {
        super(resources, 1, paramClasses, paramNames, paramDefaults);
    }

    /** Returns <code>true</code> since renderable operation is supported. */
    public boolean isRenderableSupported() {
        return true;
    }

    /**
     * Returns an array of <code>PropertyGenerators</code> implementing property inheritance for the "Rescale" operation
     *
     * @return An array of property generators.
     */
    public PropertyGenerator[] getPropertyGenerators() {
        PropertyGenerator[] pg = new PropertyGenerator[1];
        pg[0] = new RescalePropertyGenerator();
        return pg;
    }

    /**
     * Maps the pixels values of an image from one range to another range.
     *
     * <p>Creates a <code>ParameterBlockJAI</code> from all supplied arguments except <code>hints</code> and invokes
     * {@link JAI#create(String,ParameterBlock,RenderingHints)}.
     *
     * @see JAI
     * @see ParameterBlockJAI
     * @see RenderedOp
     * @param source0 <code>RenderedImage</code> source 0.
     * @param constants The per-band constants to multiply by. May be <code>null</code>.
     * @param offsets The per-band offsets to be added. May be <code>null</code>.
     * @param hints The <code>RenderingHints</code> to use. May be <code>null</code>.
     * @return The <code>RenderedOp</code> destination.
     * @throws IllegalArgumentException if <code>source0</code> is <code>null</code>.
     */
    public static RenderedOp create(RenderedImage source0, double[] constants, double[] offsets, RenderingHints hints) {
        ParameterBlockJAI pb = new ParameterBlockJAI("Rescale", RenderedRegistryMode.MODE_NAME);

        pb.setSource("source0", source0);

        pb.setParameter("constants", constants);
        pb.setParameter("offsets", offsets);

        return JAI.create("Rescale", pb, hints);
    }

    /**
     * Maps the pixels values of an image from one range to another range.
     *
     * <p>Creates a <code>ParameterBlockJAI</code> from all supplied arguments except <code>hints</code> and invokes
     * {@link JAI#create(String,ParameterBlock,RenderingHints)}.
     *
     * @param source0 <code>RenderedImage</code> source 0.
     * @param scales The per-band scale factors to multiply by.
     * @param offsets The per-band offsets to be added.
     * @param roi Optional ROI used for computations.
     * @param noData Optional No Data range used for computations.
     * @param useROIAccessor Boolean indicating if ROI RasterAccessor must be used.
     * @param destinationNoData Destination value for No Data.
     * @param hints The <code>RenderingHints</code> to use.
     * @return The <code>RenderedOp</code> destination.
     * @throws IllegalArgumentException if <code>source0</code> is <code>null</code>.
     */
    public static RenderedOp create(
            RenderedImage source0,
            double[] constants,
            double[] offsets,
            ROI roi,
            Range rangeND,
            boolean useRoiAccessor,
            double destNoData,
            RenderingHints hints) {
        // Creation of the parameterBlock object associated to the operation
        ParameterBlockJAI pb = new ParameterBlockJAI("Rescale", RenderedRegistryMode.MODE_NAME);
        // Setting of the source
        pb.setSource("source0", source0);
        // Setting of the parameters
        pb.setParameter("constants", constants);
        pb.setParameter("offsets", offsets);
        pb.setParameter("ROI", roi);
        pb.setParameter("noData", rangeND);
        pb.setParameter("useRoiAccessor", useRoiAccessor);
        pb.setParameter("destNoData", destNoData);

        return JAI.create("Rescale", pb, hints);
    }

    /**
     * Maps the pixels values of an image from one range to another range.
     *
     * <p>Creates a <code>ParameterBlockJAI</code> from all supplied arguments except <code>hints</code> and invokes
     * {@link JAI#createRenderable(String, ParameterBlock, RenderingHints)}.
     *
     * @param source0 <code>RenderedImage</code> source 0.
     * @param scales The per-band scale factors to multiply by.
     * @param offsets The per-band offsets to be added.
     * @param roi Optional ROI used for computations.
     * @param noData Optional No Data range used for computations.
     * @param useROIAccessor Boolean indicating if ROI RasterAccessor must be used.
     * @param destinationNoData Destination value for No Data.
     * @param hints The <code>RenderingHints</code> to use.
     * @return The <code>RenderedOp</code> destination.
     * @throws IllegalArgumentException if <code>source0</code> is <code>null</code>.
     */
    public static RenderableOp createRenderable(
            RenderableImage source0,
            double[] constants,
            double[] offsets,
            ROI roi,
            Range rangeND,
            boolean useRoiAccessor,
            double destNoData,
            RenderingHints hints) {
        // Creation of the parameterBlock object associated to the operation
        ParameterBlockJAI pb = new ParameterBlockJAI("Rescale", RenderableRegistryMode.MODE_NAME);
        // Setting of the source
        pb.setSource("source0", source0);
        // Setting of the parameters
        pb.setParameter("constants", constants);
        pb.setParameter("offsets", offsets);
        pb.setParameter("ROI", roi);
        pb.setParameter("noData", rangeND);
        pb.setParameter("useRoiAccessor", useRoiAccessor);
        pb.setParameter("destNoData", destNoData);

        return JAI.createRenderable("Rescale", pb, hints);
    }
}
